/*
===========================================================================

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "Precompiled.h"
#include "globaldata.h"

#include "i_system.h"
#include "z_zone.h"
#include "m_random.h"

#include "doomdef.h"
#include "p_local.h"
#include "sounds.h"

#include "st_stuff.h"
#include "hu_stuff.h"

#include "s_sound.h"

#include "doomstat.h"

extern bool globalNetworking;

void G_PlayerReborn( int player );
void P_SpawnMapThing( mapthing_t*	mthing );


//
// P_SetMobjState
// Returns true if the mobj is still present.
//

qboolean
P_SetMobjState
( mobj_t*	mobj,
  statenum_t	state )
{
	const state_t*	st;

	do
	{
		if( state == S_NULL )
		{
			mobj->state = ( const state_t* ) S_NULL;
			P_RemoveMobj( mobj );
			return false;
		}

		st = &::g->states[state];
		mobj->state = st;
		mobj->tics = st->tics;
		mobj->sprite = st->sprite;
		mobj->frame = st->frame;

		// Modified handling.
		// Call action functions when the state is set
		if( st->action )
		{
			st->action( mobj, NULL );
		}

		state = st->nextstate;
	}
	while( !mobj->tics );

	return true;
}


//
// P_ExplodeMissile
//
void P_ExplodeMissile( mobj_t* mo )
{
	mo->momx = mo->momy = mo->momz = 0;

	P_SetMobjState( mo, ( statenum_t )mobjinfo[mo->type].deathstate );

	mo->tics -= P_Random() & 3;

	if( mo->tics < 1 )
	{
		mo->tics = 1;
	}

	mo->flags &= ~MF_MISSILE;

	if( mo->info->deathsound )
	{
		S_StartSound( mo, mo->info->deathsound );
	}
}


//
// P_XYMovement
//

void P_XYMovement( mobj_t* mo )
{
	fixed_t 	ptryx;
	fixed_t	ptryy;
	player_t*	player;
	fixed_t	xmove;
	fixed_t	ymove;

	if( !mo->momx && !mo->momy )
	{
		if( mo->flags & MF_SKULLFLY )
		{
			// the skull slammed into something
			mo->flags &= ~MF_SKULLFLY;
			mo->momx = mo->momy = mo->momz = 0;

			P_SetMobjState( mo, ( statenum_t )mo->info->spawnstate );
		}
		return;
	}

	player = mo->player;

	if( mo->momx > MAXMOVE )
	{
		mo->momx = MAXMOVE;
	}
	else if( mo->momx < -MAXMOVE )
	{
		mo->momx = -MAXMOVE;
	}

	if( mo->momy > MAXMOVE )
	{
		mo->momy = MAXMOVE;
	}
	else if( mo->momy < -MAXMOVE )
	{
		mo->momy = -MAXMOVE;
	}

	xmove = mo->momx;
	ymove = mo->momy;

	do
	{
		if( xmove > MAXMOVE / 2 || ymove > MAXMOVE / 2 )
		{
			ptryx = mo->x + xmove / 2;
			ptryy = mo->y + ymove / 2;
			xmove >>= 1;
			ymove >>= 1;
		}
		else
		{
			ptryx = mo->x + xmove;
			ptryy = mo->y + ymove;
			xmove = ymove = 0;
		}

		if( !P_TryMove( mo, ptryx, ptryy ) )
		{
			// blocked move
			if( mo->player )
			{
				// try to slide along it
				P_SlideMove( mo );
			}
			else if( mo->flags & MF_MISSILE )
			{
				// explode a missile
				if( ::g->ceilingline &&
						::g->ceilingline->backsector &&
						::g->ceilingline->backsector->ceilingpic == ::g->skyflatnum )
				{
					// Hack to prevent missiles exploding
					// against the sky.
					// Does not handle sky floors.
					P_RemoveMobj( mo );
					return;
				}
				P_ExplodeMissile( mo );
			}
			else
			{
				mo->momx = mo->momy = 0;
			}
		}
	}
	while( xmove || ymove );

	// slow down
	if( player && player->cheats & CF_NOMOMENTUM )
	{
		// debug option for no sliding at all
		mo->momx = mo->momy = 0;
		return;
	}

	if( mo->flags & ( MF_MISSILE | MF_SKULLFLY ) )
	{
		return;    // no friction for missiles ever
	}

	if( mo->z > mo->floorz )
	{
		return;    // no friction when airborne
	}

	if( mo->flags & MF_CORPSE )
	{
		// do not stop sliding
		//  if halfway off a step with some momentum
		if( mo->momx > FRACUNIT / 4
				|| mo->momx < -FRACUNIT / 4
				|| mo->momy > FRACUNIT / 4
				|| mo->momy < -FRACUNIT / 4 )
		{
			if( mo->floorz != mo->subsector->sector->floorheight )
			{
				return;
			}
		}
	}

	if( mo->momx > -STOPSPEED
			&& mo->momx < STOPSPEED
			&& mo->momy > -STOPSPEED
			&& mo->momy < STOPSPEED
			&& ( !player
				 || ( player->cmd.forwardmove == 0
					  && player->cmd.sidemove == 0 ) ) )
	{
		// if in a walking frame, stop moving
		if( player && ( unsigned )( ( player->mo->state - ::g->states ) - S_PLAY_RUN1 ) < 4 )
		{
			P_SetMobjState( player->mo, S_PLAY );
		}

		mo->momx = 0;
		mo->momy = 0;
	}
	else
	{
		mo->momx = FixedMul( mo->momx, FRICTION );
		mo->momy = FixedMul( mo->momy, FRICTION );
	}
}

//
// P_ZMovement
//
void P_ZMovement( mobj_t* mo )
{
	fixed_t	dist;
	fixed_t	delta;

	// check for smooth step up
	if( mo->player && mo->z < mo->floorz )
	{
		mo->player->viewheight -= mo->floorz - mo->z;

		mo->player->deltaviewheight
			= ( VIEWHEIGHT - mo->player->viewheight ) >> 3;
	}

	// adjust height
	mo->z += mo->momz;

	if( mo->flags & MF_FLOAT
			&& mo->target )
	{
		// float down towards target if too close
		if( !( mo->flags & MF_SKULLFLY )
				&& !( mo->flags & MF_INFLOAT ) )
		{
			dist = P_AproxDistance( mo->x - mo->target->x,
									mo->y - mo->target->y );

			delta = ( mo->target->z + ( mo->height >> 1 ) ) - mo->z;

			if( delta < 0 && dist < -( delta * 3 ) )
			{
				mo->z -= FLOATSPEED;
			}
			else if( delta > 0 && dist < ( delta * 3 ) )
			{
				mo->z += FLOATSPEED;
			}
		}

	}

	// clip movement
	if( mo->z <= mo->floorz )
	{
		// hit the floor

		// Note (id):
		//  somebody left this after the setting momz to 0,
		//  kinda useless there.
		if( mo->flags & MF_SKULLFLY )
		{
			// the skull slammed into something
			mo->momz = -mo->momz;
		}

		if( mo->momz < 0 )
		{
			if( mo->player
					&& mo->momz < -GRAVITY * 8 )
			{
				// Squat down.
				// Decrease ::g->viewheight for a moment
				// after hitting the ground (hard),
				// and utter appropriate sound.
				mo->player->deltaviewheight = mo->momz >> 3;
				if( globalNetworking || ( mo->player == &::g->players[::g->consoleplayer] ) )
				{
					S_StartSound( mo, sfx_oof );
				}
			}
			mo->momz = 0;
		}
		mo->z = mo->floorz;

		if( ( mo->flags & MF_MISSILE )
				&& !( mo->flags & MF_NOCLIP ) )
		{
			P_ExplodeMissile( mo );
			return;
		}
	}
	else if( !( mo->flags & MF_NOGRAVITY ) )
	{
		if( mo->momz == 0 )
		{
			mo->momz = -GRAVITY * 2;
		}
		else
		{
			mo->momz -= GRAVITY;
		}
	}

	if( mo->z + mo->height > mo->ceilingz )
	{
		// hit the ceiling
		if( mo->momz > 0 )
		{
			mo->momz = 0;
		}
		{
			mo->z = mo->ceilingz - mo->height;
		}

		if( mo->flags & MF_SKULLFLY )
		{
			// the skull slammed into something
			mo->momz = -mo->momz;
		}

		if( ( mo->flags & MF_MISSILE )
				&& !( mo->flags & MF_NOCLIP ) )
		{
			P_ExplodeMissile( mo );
			return;
		}
	}
}



//
// P_NightmareRespawn
//
void
P_NightmareRespawn( mobj_t* mobj )
{
	fixed_t		x;
	fixed_t		y;
	fixed_t		z;
	subsector_t*	ss;
	mobj_t*		mo;
	mapthing_t*		mthing;

	x = mobj->spawnpoint.x << FRACBITS;
	y = mobj->spawnpoint.y << FRACBITS;

	// somthing is occupying it's position?
	if( !P_CheckPosition( mobj, x, y ) )
	{
		return;    // no respwan
	}

	// spawn a teleport fog at old spot
	// because of removal of the body?
	mo = P_SpawnMobj( mobj->x,
					  mobj->y,
					  mobj->subsector->sector->floorheight , MT_TFOG );
	// initiate teleport sound
	S_StartSound( mo, sfx_telept );

	// spawn a teleport fog at the new spot
	ss = R_PointInSubsector( x, y );

	mo = P_SpawnMobj( x, y, ss->sector->floorheight , MT_TFOG );

	S_StartSound( mo, sfx_telept );

	// spawn the new monster
	mthing = &mobj->spawnpoint;

	// spawn it
	if( mobj->info->flags & MF_SPAWNCEILING )
	{
		z = ONCEILINGZ;
	}
	else
	{
		z = ONFLOORZ;
	}

	// inherit attributes from deceased one
	mo = P_SpawnMobj( x, y, z, mobj->type );
	mo->spawnpoint = mobj->spawnpoint;
	mo->angle = ANG45 * ( mthing->angle / 45 );

	if( mthing->options & MTF_AMBUSH )
	{
		mo->flags |= MF_AMBUSH;
	}

	mo->reactiontime = 18;

	// remove the old monster,
	P_RemoveMobj( mobj );
}


//
// P_MobjThinker
//
void P_MobjThinker( mobj_t* mobj )
{
	// momentum movement
	if( mobj->momx
			|| mobj->momy
			|| ( mobj->flags & MF_SKULLFLY ) )
	{
		P_XYMovement( mobj );

		// FIXME: decent NOP/NULL/Nil function pointer please.
		if( mobj->thinker.function.acv == ( actionf_v )( -1 ) )
		{
			return;    // mobj was removed
		}
	}
	if( ( mobj->z != mobj->floorz )
			|| mobj->momz )
	{
		P_ZMovement( mobj );

		// FIXME: decent NOP/NULL/Nil function pointer please.
		if( mobj->thinker.function.acv == ( actionf_v )( -1 ) )
		{
			return;    // mobj was removed
		}
	}


	// cycle through states,
	// calling action functions at transitions
	if( mobj->tics != -1 )
	{
		mobj->tics--;

		// you can cycle through multiple states in a tic
		if( !mobj->tics )
			if( !P_SetMobjState( mobj, mobj->state->nextstate ) )
			{
				return;    // freed itself
			}
	}
	else
	{
		// check for nightmare respawn
		if( !( mobj->flags & MF_COUNTKILL ) )
		{
			return;
		}

		if( !::g->respawnmonsters )
		{
			return;
		}

		mobj->movecount++;

		if( mobj->movecount < 12 * TICRATE )
		{
			return;
		}

		if( ::g->leveltime & 31 )
		{
			return;
		}

		if( P_Random() > 4 )
		{
			return;
		}

		P_NightmareRespawn( mobj );
	}

}


//
// P_SpawnMobj
//
mobj_t*
P_SpawnMobj
( fixed_t	x,
  fixed_t	y,
  fixed_t	z,
  mobjtype_t	type )
{
	mobj_t*	mobj;
	const state_t*	st;
	const mobjinfo_t*	info;

	mobj = ( mobj_t* )DoomLib::Z_Malloc( sizeof( *mobj ), PU_LEVEL, NULL );
	memset( mobj, 0, sizeof( *mobj ) );
	info = &mobjinfo[type];

	mobj->type = type;
	mobj->info = info;
	mobj->x = x;
	mobj->y = y;
	mobj->radius = info->radius;
	mobj->height = info->height;
	mobj->flags = info->flags;
	mobj->health = info->spawnhealth;

	if( ::g->gameskill != sk_nightmare )
	{
		mobj->reactiontime = info->reactiontime;
	}

	mobj->lastlook = P_Random() % MAXPLAYERS;
	// do not set the state with P_SetMobjState,
	// because action routines can not be called yet
	st = &::g->states[info->spawnstate];

	mobj->state = st;
	mobj->tics = st->tics;
	mobj->sprite = st->sprite;
	mobj->frame = st->frame;

	// set subsector and/or block links
	P_SetThingPosition( mobj );

	mobj->floorz = mobj->subsector->sector->floorheight;
	mobj->ceilingz = mobj->subsector->sector->ceilingheight;

	if( z == ONFLOORZ )
	{
		mobj->z = mobj->floorz;
	}
	else if( z == ONCEILINGZ )
	{
		mobj->z = mobj->ceilingz - mobj->info->height;
	}
	else
	{
		mobj->z = z;
	}

	mobj->thinker.function.acp1 = ( actionf_p1 )P_MobjThinker;

	P_AddThinker( &mobj->thinker );

	return mobj;
}


//
// P_RemoveMobj
//


void P_RemoveMobj( mobj_t* mobj )
{
	if( ( mobj->flags & MF_SPECIAL )
			&& !( mobj->flags & MF_DROPPED )
			&& ( mobj->type != MT_INV )
			&& ( mobj->type != MT_INS ) )
	{
		::g->itemrespawnque[::g->iquehead] = mobj->spawnpoint;
		::g->itemrespawntime[::g->iquehead] = ::g->leveltime;
		::g->iquehead = ( ::g->iquehead + 1 ) & ( ITEMQUESIZE - 1 );

		// lose one off the end?
		if( ::g->iquehead == ::g->iquetail )
		{
			::g->iquetail = ( ::g->iquetail + 1 ) & ( ITEMQUESIZE - 1 );
		}
	}

	// unlink from sector and block lists
	P_UnsetThingPosition( mobj );

	// stop any playing sound
	//S_StopSound (mobj);

	// free block
	P_RemoveThinker( ( thinker_t* )mobj );
}




//
// P_RespawnSpecials
//
void P_RespawnSpecials( void )
{
	fixed_t		x;
	fixed_t		y;
	fixed_t		z;

	subsector_t*	ss;
	mobj_t*		mo;
	mapthing_t*		mthing;

	int			i;

	// only respawn items in ::g->deathmatch
	if( ::g->deathmatch != 2 )
	{
		return;    //
	}

	// nothing left to respawn?
	if( ::g->iquehead == ::g->iquetail )
	{
		return;
	}

	// wait at least 30 seconds
	if( ::g->leveltime - ::g->itemrespawntime[::g->iquetail] < 30 * TICRATE )
	{
		return;
	}

	mthing = &::g->itemrespawnque[::g->iquetail];

	x = mthing->x << FRACBITS;
	y = mthing->y << FRACBITS;

	// spawn a teleport fog at the new spot
	ss = R_PointInSubsector( x, y );
	mo = P_SpawnMobj( x, y, ss->sector->floorheight , MT_IFOG );
	S_StartSound( mo, sfx_itmbk );

	// find which type to spawn
	for( i = 0 ; i < NUMMOBJTYPES ; i++ )
	{
		if( mthing->type == mobjinfo[i].doomednum )
		{
			break;
		}
	}

	// spawn it
	if( mobjinfo[i].flags & MF_SPAWNCEILING )
	{
		z = ONCEILINGZ;
	}
	else
	{
		z = ONFLOORZ;
	}

	mo = ( mobj_t* )P_SpawnMobj( x, y, z, ( mobjtype_t )i );
	mo->spawnpoint = *mthing;
	mo->angle = ANG45 * ( mthing->angle / 45 );

	// pull it from the que
	::g->iquetail = ( ::g->iquetail + 1 ) & ( ITEMQUESIZE - 1 );
}




//
// P_SpawnPlayer
// Called when a player is spawned on the level.
// Most of the player structure stays unchanged
//  between levels.
//
void P_SpawnPlayer( mapthing_t* mthing )
{
	player_t*		p;
	fixed_t		x;
	fixed_t		y;
	fixed_t		z;

	mobj_t*		mobj;

	int			i;

	// not playing?
	if( !::g->playeringame[mthing->type - 1] )
	{
		return;
	}

	p = &::g->players[mthing->type - 1];

	if( p->playerstate == PST_REBORN )
	{
		G_PlayerReborn( mthing->type - 1 );
	}

	x 		= mthing->x << FRACBITS;
	y 		= mthing->y << FRACBITS;
	z		= ONFLOORZ;
	mobj	= P_SpawnMobj( x, y, z, MT_PLAYER );

	// set color translations for player ::g->sprites
	if( mthing->type > 1 )
	{
		mobj->flags |= ( mthing->type - 1 ) << MF_TRANSSHIFT;
	}

	mobj->angle	= ANG45 * ( mthing->angle / 45 );
	mobj->player = p;
	mobj->health = p->health;

	p->mo = mobj;
	p->playerstate = PST_LIVE;
	p->refire = 0;
	p->message = NULL;
	p->damagecount = 0;
	p->bonuscount = 0;
	p->extralight = 0;
	p->fixedcolormap = 0;
	p->viewheight = VIEWHEIGHT;

	// setup gun psprite
	P_SetupPsprites( p );

	// give all cards in death match mode
	if( ::g->deathmatch )
		for( i = 0 ; i < NUMCARDS ; i++ )
		{
			p->cards[i] = true;
		}

	if( mthing->type - 1 == ::g->consoleplayer )
	{
		// wake up the status bar
		ST_Start();
		// wake up the heads up text
		HU_Start();
	}

	// Give him everything is Give All is on.
	if( p->cheats & CF_GIVEALL )
	{
		p->armorpoints = 200;
		p->armortype = 2;

		int i;
		for( i = 0; i < NUMWEAPONS; i++ )
		{
			p->weaponowned[i] = true;
		}

		for( i = 0; i < NUMAMMO; i++ )
		{
			p->ammo[i] =  p->maxammo[i];
		}

		for( i = 0; i < NUMCARDS; i++ )
		{
			p->cards[i] = true;
		}
	}

}


//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//
void P_SpawnMapThing( mapthing_t* mthing )
{
	int			i;
	int			bit;
	mobj_t*		mobj;
	fixed_t		x;
	fixed_t		y;
	fixed_t		z;

	// count ::g->deathmatch start positions
	if( mthing->type == 11 )
	{
		if( ::g->deathmatch_p < &::g->deathmatchstarts[10] )
		{
			memcpy( ::g->deathmatch_p, mthing, sizeof( *mthing ) );
			::g->deathmatch_p++;
		}
		return;
	}

	// check for ::g->players specially
	if( mthing->type <= 4 )
	{
		// save spots for respawning in network games
		::g->playerstarts[mthing->type - 1] = *mthing;
		if( !::g->deathmatch )
		{
			P_SpawnPlayer( mthing );
		}

		return;
	}

	// check for apropriate skill level
	if( !::g->netgame && ( mthing->options & 16 ) )
	{
		return;
	}

	if( ::g->gameskill == sk_baby )
	{
		bit = 1;
	}
	else if( ::g->gameskill == sk_nightmare )
	{
		bit = 4;
	}
	else
	{
		bit = 1 << ( ::g->gameskill - 1 );
	}

	if( !( mthing->options & bit ) )
	{
		return;
	}

	// find which type to spawn
	for( i = 0 ; i < NUMMOBJTYPES ; i++ )
		if( mthing->type == mobjinfo[i].doomednum )
		{
			break;
		}

	if( i == NUMMOBJTYPES )
	{
		//printf( "P_SpawnMapThing: Unknown type %i at (%i, %i)", mthing->type, mthing->x, mthing->y);
		return;
		//I_Error ("P_SpawnMapThing: Unknown type %i at (%i, %i)",
		//mthing->type,
		//mthing->x, mthing->y);
	}

	// don't spawn keycards and ::g->players in ::g->deathmatch
	if( ::g->deathmatch && mobjinfo[i].flags & MF_NOTDMATCH )
	{
		return;
	}

	// don't spawn any monsters if -::g->nomonsters
	if( ::g->nomonsters
			&& ( i == MT_SKULL
				 || ( mobjinfo[i].flags & MF_COUNTKILL ) ) )
	{
		return;
	}

	// spawn it
	x = mthing->x << FRACBITS;
	y = mthing->y << FRACBITS;

	if( mobjinfo[i].flags & MF_SPAWNCEILING )
	{
		z = ONCEILINGZ;
	}
	else
	{
		z = ONFLOORZ;
	}

	mobj = ( mobj_t* )P_SpawnMobj( x, y, z, ( mobjtype_t )i );
	mobj->spawnpoint = *mthing;

	if( mobj->tics > 0 )
	{
		mobj->tics = 1 + ( P_Random() % mobj->tics );
	}
	if( mobj->flags & MF_COUNTKILL )
	{
		::g->totalkills++;
	}
	if( mobj->flags & MF_COUNTITEM )
	{
		::g->totalitems++;
	}

	mobj->angle = ANG45 * ( mthing->angle / 45 );
	if( mthing->options & MTF_AMBUSH )
	{
		mobj->flags |= MF_AMBUSH;
	}
}



//
// GAME SPAWN FUNCTIONS
//


//
// P_SpawnPuff
//

void
P_SpawnPuff
( fixed_t	x,
  fixed_t	y,
  fixed_t	z )
{
	mobj_t*	th;

	z += ( ( P_Random() - P_Random() ) << 10 );

	th = P_SpawnMobj( x, y, z, MT_PUFF );
	th->momz = FRACUNIT;
	th->tics -= P_Random() & 3;

	if( th->tics < 1 )
	{
		th->tics = 1;
	}

	// don't make punches spark on the wall
	if( ::g->attackrange == MELEERANGE )
	{

		P_SetMobjState( th, S_PUFF3 );

	}
}



//
// P_SpawnBlood
//
void
P_SpawnBlood
( fixed_t	x,
  fixed_t	y,
  fixed_t	z,
  int		damage )
{
	mobj_t*	th;

	z += ( ( P_Random() - P_Random() ) << 10 );
	th = P_SpawnMobj( x, y, z, MT_BLOOD );
	th->momz = FRACUNIT * 2;
	th->tics -= P_Random() & 3;

	if( th->tics < 1 )
	{
		th->tics = 1;
	}

	if( damage <= 12 && damage >= 9 )
	{
		P_SetMobjState( th, S_BLOOD2 );
	}
	else if( damage < 9 )
	{
		P_SetMobjState( th, S_BLOOD3 );
	}
}



//
// P_CheckMissileSpawn
// Moves the missile forward a bit
//  and possibly explodes it right there.
//
void P_CheckMissileSpawn( mobj_t* th )
{
	th->tics -= P_Random() & 3;
	if( th->tics < 1 )
	{
		th->tics = 1;
	}

	// move a little forward so an angle can
	// be computed if it immediately explodes
	th->x += ( th->momx >> 1 );
	th->y += ( th->momy >> 1 );
	th->z += ( th->momz >> 1 );

	if( !P_TryMove( th, th->x, th->y ) )
	{
		P_ExplodeMissile( th );
	}
}


//
// P_SpawnMissile
//
mobj_t*
P_SpawnMissile
( mobj_t*	source,
  mobj_t*	dest,
  mobjtype_t	type )
{
	mobj_t*	th;
	angle_t	an;
	int		dist;

	th = P_SpawnMobj( source->x,
					  source->y,
					  source->z + 4 * 8 * FRACUNIT, type );

	if( th->info->seesound )
	{
		S_StartSound( th, th->info->seesound );
	}

	th->target = source;	// where it came from
	an = R_PointToAngle2( source->x, source->y, dest->x, dest->y );

	// fuzzy player
	if( dest->flags & MF_SHADOW )
	{
		an += ( P_Random() - P_Random() ) << 20;
	}

	th->angle = an;
	an >>= ANGLETOFINESHIFT;
	th->momx = FixedMul( th->info->speed, finecosine[an] );
	th->momy = FixedMul( th->info->speed, finesine[an] );

	dist = P_AproxDistance( dest->x - source->x, dest->y - source->y );
	dist = dist / th->info->speed;

	if( dist < 1 )
	{
		dist = 1;
	}

	th->momz = ( dest->z - source->z ) / dist;
	P_CheckMissileSpawn( th );

	return th;
}


//
// P_SpawnPlayerMissile
// Tries to aim at a nearby monster
//
void
P_SpawnPlayerMissile
( mobj_t*	source,
  mobjtype_t	type )
{
	mobj_t*	th;
	angle_t	an;

	fixed_t	x;
	fixed_t	y;
	fixed_t	z;
	fixed_t	slope;

	// see which target is to be aimed at
	an = source->angle;
	slope = P_AimLineAttack( source, an, 16 * 64 * FRACUNIT );

	if( !::g->linetarget )
	{
		an += 1 << 26;
		slope = P_AimLineAttack( source, an, 16 * 64 * FRACUNIT );

		if( !::g->linetarget )
		{
			an -= 2 << 26;
			slope = P_AimLineAttack( source, an, 16 * 64 * FRACUNIT );
		}

		if( !::g->linetarget )
		{
			an = source->angle;
			slope = 0;
		}
	}

	x = source->x;
	y = source->y;
	z = source->z + 4 * 8 * FRACUNIT;

	th = P_SpawnMobj( x, y, z, type );

	if( th->info->seesound && ( source->player == &::g->players[::g->consoleplayer] ) )
	{
		S_StartSound( th, th->info->seesound );
	}

	th->target = source;
	th->angle = an;
	th->momx = FixedMul( th->info->speed,
						 finecosine[an >> ANGLETOFINESHIFT] );
	th->momy = FixedMul( th->info->speed,
						 finesine[an >> ANGLETOFINESHIFT] );
	th->momz = FixedMul( th->info->speed, slope );

	P_CheckMissileSpawn( th );
}


